#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
HQ-Reader PyInstaller 构建脚本
支持 Windows、macOS 和 Linux 平台的应用程序打包
"""

import os
import sys
import shutil
import subprocess
import platform
import zipfile
import tarfile
from pathlib import Path
from datetime import datetime
import argparse


class Colors:
    """终端颜色"""
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKCYAN = '\033[96m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'


class BuildManager:
    """构建管理器"""
    
    def __init__(self, clean=False, archive=True):
        # 项目根目录是packaging文件夹的上级目录
        self.project_root = Path(__file__).parent.parent
        self.packaging_dir = Path(__file__).parent
        self.build_dir = self.project_root / "build"
        self.dist_dir = self.project_root / "dist"
        self.spec_file = self.packaging_dir / "hq_reader.spec"
        
        self.clean = clean
        self.archive = archive
        
        # 平台信息
        self.system = platform.system()
        self.arch = platform.machine()
        self.is_windows = self.system == "Windows"
        self.is_macos = self.system == "Darwin"
        self.is_linux = self.system == "Linux"
        
        # 应用信息
        self.app_name = "HQ-Reader"
        self.app_version = "1.0.0"
        
        # 构建时间
        self.build_time = datetime.now()
        
    def log(self, message, level="INFO"):
        """打印带颜色的日志"""
        timestamp = self.build_time.strftime("%H:%M:%S")
        
        color = Colors.ENDC
        if level == "INFO":
            color = Colors.OKBLUE
        elif level == "SUCCESS":
            color = Colors.OKGREEN
        elif level == "WARNING":
            color = Colors.WARNING
        elif level == "ERROR":
            color = Colors.FAIL
        elif level == "HEADER":
            color = Colors.HEADER + Colors.BOLD
            
        print(f"{color}[{timestamp}] {level}: {message}{Colors.ENDC}")
        
    def run_command(self, command, cwd=None):
        """运行命令"""
        self.log(f"执行命令: {' '.join(command)}")
        try:
            result = subprocess.run(
                command,
                cwd=cwd or self.project_root,
                check=True,
                capture_output=True,
                text=True
            )
            if result.stdout.strip():
                self.log(f"输出: {result.stdout.strip()}")
            return True
        except subprocess.CalledProcessError as e:
            self.log(f"命令执行失败: {e}", "ERROR")
            if e.stderr:
                self.log(f"错误: {e.stderr}", "ERROR")
            return False
        except FileNotFoundError:
            self.log(f"命令未找到: {command[0]}", "ERROR")
            return False
            
    def check_dependencies(self):
        """检查构建依赖"""
        self.log("检查构建依赖...", "HEADER")
        
        # 检查Python版本
        python_version = sys.version_info
        if python_version < (3, 8):
            self.log("Python版本需要 >= 3.8", "ERROR")
            return False
        self.log(f"Python版本: {python_version.major}.{python_version.minor}.{python_version.micro}", "SUCCESS")
        
        # 检查PyInstaller
        try:
            import PyInstaller
            self.log(f"PyInstaller版本: {PyInstaller.__version__}", "SUCCESS")
        except ImportError:
            self.log("PyInstaller未安装，正在安装...", "WARNING")
            if not self.run_command([sys.executable, "-m", "pip", "install", "pyinstaller"]):
                return False
                
        # 检查规格文件
        if not self.spec_file.exists():
            self.log("规格文件不存在", "ERROR")
            return False
        self.log("规格文件检查通过", "SUCCESS")
        
        return True
        
    def clean_build(self):
        """清理构建目录"""
        if self.clean:
            self.log("清理构建目录...", "HEADER")
            for directory in [self.build_dir, self.dist_dir]:
                if directory.exists():
                    shutil.rmtree(directory)
                    self.log(f"已删除: {directory}")
                    
    def build_application(self):
        """构建应用程序"""
        self.log("开始构建应用程序...", "HEADER")
        
        # 预先构建matplotlib字体缓存
        self.log("预先构建matplotlib字体缓存...", "INFO")
        try:
            sys.path.insert(0, str(self.project_root))
            from utils.matplotlib_init import init_matplotlib
            if init_matplotlib():
                self.log("Matplotlib字体缓存预构建成功", "SUCCESS")
            else:
                self.log("Matplotlib字体缓存预构建失败，继续构建过程", "WARNING")
        except Exception as e:
            self.log(f"Matplotlib字体缓存预构建异常: {e}", "WARNING")
        
        # 运行PyInstaller
        command = [
            sys.executable, "-m", "PyInstaller",
            "--clean",
            "--noconfirm", 
            str(self.spec_file)
        ]
        
        if not self.run_command(command):
            return False
            
        # 检查构建结果
        if self.is_macos:
            app_path = self.dist_dir / f"{self.app_name}.app"
            app_dir = self.dist_dir / self.app_name  # onedir模式的目录
            if not app_path.exists() and not app_dir.exists():
                self.log("macOS应用程序包未生成", "ERROR")
                return False
            if app_path.exists():
                self.log(f"macOS应用程序包已生成: {app_path}", "SUCCESS")
            if app_dir.exists():
                self.log(f"应用程序目录已生成: {app_dir}", "SUCCESS")
            
        elif self.is_windows:
            exe_path = self.dist_dir / f"{self.app_name}.exe"
            app_dir = self.dist_dir / self.app_name  # onedir模式的目录
            if not exe_path.exists() and not app_dir.exists():
                self.log("Windows可执行文件未生成", "ERROR")
                return False
            if exe_path.exists():
                self.log(f"Windows可执行文件已生成: {exe_path}", "SUCCESS")
            if app_dir.exists():
                self.log(f"应用程序目录已生成: {app_dir}", "SUCCESS")
            
        else:  # Linux
            exe_path = self.dist_dir / self.app_name
            app_dir = self.dist_dir / self.app_name  # onedir模式的目录
            if not exe_path.exists() and not app_dir.exists():
                self.log("Linux可执行文件未生成", "ERROR")
                return False
            if exe_path.exists():
                self.log(f"Linux可执行文件已生成: {exe_path}", "SUCCESS")
            if app_dir.exists():
                self.log(f"应用程序目录已生成: {app_dir}", "SUCCESS")
            
        return True
        
    def copy_file_association_scripts(self):
        """复制文件关联脚本到输出目录"""
        self.log("复制文件关联脚本...", "HEADER")
        
        # 确定输出目录
        if self.is_macos:
            # macOS: 检查是否有.app包或目录
            app_path = self.dist_dir / f"{self.app_name}.app"
            app_dir = self.dist_dir / self.app_name
            if app_path.exists():
                target_dir = self.dist_dir  # 将脚本放在.app同级目录
            elif app_dir.exists():
                target_dir = app_dir
            else:
                self.log("找不到macOS应用程序输出", "ERROR")
                return False
        elif self.is_windows:
            # Windows: 检查是否有.exe或目录
            exe_path = self.dist_dir / f"{self.app_name}.exe"
            app_dir = self.dist_dir / self.app_name
            if exe_path.exists():
                target_dir = self.dist_dir
            elif app_dir.exists():
                target_dir = app_dir
            else:
                self.log("找不到Windows应用程序输出", "ERROR")
                return False
        else:  # Linux
            # Linux: 检查是否有可执行文件或目录
            exe_path = self.dist_dir / self.app_name
            app_dir = self.dist_dir / self.app_name
            if exe_path.exists() and exe_path.is_file():
                target_dir = self.dist_dir
            elif app_dir.exists():
                target_dir = app_dir
            else:
                self.log("找不到Linux应用程序输出", "ERROR")
                return False
        
        # 复制对应平台的文件关联脚本
        try:
            if self.is_windows:
                # 复制Windows注册脚本
                scripts = [
                    "register_file_associations_windows.bat",
                    "unregister_file_associations_windows.bat"
                ]
                for script in scripts:
                    src = self.packaging_dir / script
                    if src.exists():
                        dst = target_dir / script
                        shutil.copy2(src, dst)
                        self.log(f"已复制: {script}")
                    else:
                        self.log(f"脚本不存在: {script}", "WARNING")
                        
            elif self.is_linux:
                # 复制Linux安装脚本和配置文件
                files = [
                    "install_linux.sh",
                    "uninstall_linux.sh", 
                    "hq-reader.desktop",
                    "hq-reader-mimetypes.xml"
                ]
                for file in files:
                    src = self.packaging_dir / file
                    if src.exists():
                        dst = target_dir / file
                        shutil.copy2(src, dst)
                        # 为shell脚本设置可执行权限
                        if file.endswith('.sh'):
                            os.chmod(dst, 0o755)
                        self.log(f"已复制: {file}")
                    else:
                        self.log(f"文件不存在: {file}", "WARNING")
                        
            elif self.is_macos:
                # macOS的文件关联已在Info.plist中配置，无需额外脚本
                self.log("macOS文件关联已在应用程序包中配置", "SUCCESS")
                
            # 复制使用指南（所有平台）
            guide_file = "FILE_ASSOCIATION_GUIDE.md"
            src = self.packaging_dir / guide_file
            if src.exists():
                dst = target_dir / guide_file
                shutil.copy2(src, dst)
                self.log(f"已复制: {guide_file}")
            else:
                self.log(f"指南文件不存在: {guide_file}", "WARNING")
                
            return True
            
        except Exception as e:
            self.log(f"复制文件关联脚本失败: {e}", "ERROR")
            return False
        
    def create_dmg_macos(self):
        """为macOS创建DMG文件"""
        self.log("创建macOS DMG文件...", "HEADER")
        
        try:
            # 导入DMG创建器
            sys.path.insert(0, str(self.packaging_dir))
            from create_dmg import DMGCreator
            
            # 创建DMG
            dmg_creator = DMGCreator(self.app_name, self.app_version)
            if dmg_creator.create_dmg():
                self.log("DMG文件创建成功", "SUCCESS")
                return True
            else:
                self.log("DMG文件创建失败", "ERROR")
                return False
                
        except ImportError:
            self.log("DMG创建脚本不存在，跳过DMG创建", "WARNING")
            return True
        except Exception as e:
            self.log(f"DMG创建出错: {e}", "ERROR")
            return False
        
    def create_archive(self):
        """创建分发包"""
        if not self.archive:
            return True
            
        self.log("创建分发包...", "HEADER")
        
        # 确定平台标识
        platform_name = self.system.lower()
        if self.arch.lower() in ["x86_64", "amd64"]:
            arch_name = "x64"
        elif self.arch.lower() in ["aarch64", "arm64"]:
            arch_name = "arm64"
        else:
            arch_name = self.arch.lower()
            
        timestamp = self.build_time.strftime("%Y%m%d_%H%M%S")
        
        if self.is_macos:
            # macOS: 创建DMG文件
            if self.create_dmg_macos():
                self.log("macOS DMG分发包创建完成", "SUCCESS")
            else:
                # 如果DMG创建失败，回退到ZIP
                self.log("DMG创建失败，创建ZIP备用包...", "WARNING")
                app_path = self.dist_dir / f"{self.app_name}.app"
                app_dir = self.dist_dir / self.app_name
                
                archive_name = f"{self.app_name}_{self.app_version}_{platform_name}_{arch_name}_{timestamp}.zip"
                archive_path = self.dist_dir / archive_name
                
                with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                    if app_path.exists():
                        # 打包.app文件
                        for root, dirs, files in os.walk(app_path):
                            for file in files:
                                file_path = Path(root) / file
                                arcname = file_path.relative_to(self.dist_dir)
                                zipf.write(file_path, arcname)
                    elif app_dir.exists():
                        # 打包目录
                        for root, dirs, files in os.walk(app_dir):
                            for file in files:
                                file_path = Path(root) / file
                                arcname = file_path.relative_to(self.dist_dir)
                                zipf.write(file_path, arcname)
                                
                self.log(f"ZIP备用包已创建: {archive_path}", "SUCCESS")
                        
        elif self.is_windows:
            # Windows: 创建ZIP
            exe_path = self.dist_dir / f"{self.app_name}.exe"
            app_dir = self.dist_dir / self.app_name
            
            archive_name = f"{self.app_name}_{self.app_version}_{platform_name}_{arch_name}_{timestamp}.zip"
            archive_path = self.dist_dir / archive_name
            
            with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                if exe_path.exists():
                    zipf.write(exe_path, exe_path.name)
                elif app_dir.exists():
                    for root, dirs, files in os.walk(app_dir):
                        for file in files:
                            file_path = Path(root) / file
                            arcname = file_path.relative_to(self.dist_dir)
                            zipf.write(file_path, arcname)
                
        else:  # Linux
            # Linux: 创建tar.gz
            exe_path = self.dist_dir / self.app_name
            app_dir = self.dist_dir / self.app_name
            
            archive_name = f"{self.app_name}_{self.app_version}_{platform_name}_{arch_name}_{timestamp}.tar.gz"
            archive_path = self.dist_dir / archive_name
            
            with tarfile.open(archive_path, 'w:gz') as tarf:
                if exe_path.exists() and exe_path.is_file():
                    tarf.add(exe_path, exe_path.name)
                elif app_dir.exists():
                    tarf.add(app_dir, self.app_name)
                
        if not self.is_macos or not self.create_dmg_macos():
            self.log(f"分发包已创建: {archive_path}", "SUCCESS")
            
        return True
        
    def print_summary(self):
        """打印构建摘要"""
        self.log("构建摘要", "HEADER")
        self.log(f"应用程序: {self.app_name} v{self.app_version}")
        self.log(f"平台: {self.system} ({self.arch})")
        self.log(f"构建时间: {self.build_time.strftime('%Y-%m-%d %H:%M:%S')}")
        
        # 列出生成的文件
        if self.dist_dir.exists():
            files = list(self.dist_dir.iterdir())
            if files:
                self.log("生成的文件:")
                for file in files:
                    if file.is_file():
                        size = file.stat().st_size
                        if size > 1024 * 1024:
                            size_str = f"{size / (1024 * 1024):.1f} MB"
                        elif size > 1024:
                            size_str = f"{size / 1024:.1f} KB"
                        else:
                            size_str = f"{size} B"
                    else:
                        size_str = "目录"
                    self.log(f"  - {file.name} ({size_str})")
                    
    def build(self):
        """执行完整构建流程"""
        self.log(f"开始构建 {self.app_name} v{self.app_version}", "HEADER")
        self.log(f"目标平台: {self.system} ({self.arch})")
        
        try:
            # 检查依赖
            if not self.check_dependencies():
                return False
                
            # 清理构建目录
            self.clean_build()
            
            # 构建应用程序
            if not self.build_application():
                return False
                
            # 复制文件关联脚本
            if not self.copy_file_association_scripts():
                return False
                
            # 创建分发包
            if not self.create_archive():
                return False
                
            # 打印摘要
            self.print_summary()
            
            self.log("构建完成！", "SUCCESS")
            return True
            
        except KeyboardInterrupt:
            self.log("构建被用户中断", "WARNING")
            return False
        except Exception as e:
            self.log(f"构建失败: {e}", "ERROR")
            return False


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description="HQ-Reader PyInstaller 构建脚本")
    parser.add_argument("--clean", action="store_true", help="清理构建目录")
    parser.add_argument("--no-archive", action="store_true", help="不创建分发包")
    
    args = parser.parse_args()
    
    # 创建构建管理器
    builder = BuildManager(
        clean=args.clean,
        archive=not args.no_archive
    )
    
    # 执行构建
    success = builder.build()
    sys.exit(0 if success else 1)


if __name__ == "__main__":
    main() 